C++智能指针

梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)


  本教程将从 C++ 智能指针的核心概念、实现原理,到 C++11 标准中各类智能指针(std::unique_ptr、std::shared_ptr、std::weak_ptr)的使用方法、特性及场景,再到 std::auto_ptr 的弃用原因,全面拆解智能指针的核心知识点,帮助你解决手动管理内存带来的泄漏、野指针等问题。

教程目录导航

一、智能指针核心概述

1.1 智能指针的基本概念

智能指针是 C++ 标准库提供的模板类,本质是对普通裸指针的封装,利用RAII(资源获取即初始化)思想,在对象生命周期结束(析构时)自动释放所管理的内存资源,避免手动调用 delete 导致的内存泄漏、野指针、二次释放等问题。

智能指针的核心目标:将内存资源的生命周期与智能指针对象的生命周期绑定,实现内存的自动管理。

1.2 手动内存管理的痛点

使用裸指针手动管理堆内存时,常见问题:


// 手动内存管理的典型问题示例
void badExample() {
    int* p = new int(10); // 分配堆内存
    
    if (true) {
        return; // 提前返回,delete未执行 → 内存泄漏
    }
    
    delete p; // 永远不会执行
    p = nullptr;
}
        

1.3 智能指针的实现原理

智能指针基于 RAII 思想 实现,核心步骤:

  1. 智能指针对象创建时,接管裸指针的内存所有权(构造函数接收裸指针);
  2. 智能指针对象提供解引用(*)、箭头(->)重载,模拟裸指针的使用方式;
  3. 智能指针对象析构时(离开作用域/生命周期结束),自动调用 delete 释放管理的内存;
  4. 通过禁用拷贝构造/赋值(如 unique_ptr)或引用计数(如 shared_ptr),解决所有权问题。

// 简易智能指针实现(仅演示原理)
template <typename T>
class SimpleSmartPtr {
private:
    T* m_ptr; // 管理的裸指针
public:
    // 构造函数:接管裸指针
    explicit SimpleSmartPtr(T* ptr = nullptr) : m_ptr(ptr) {}
    
    // 析构函数:自动释放内存
    ~SimpleSmartPtr() {
        delete m_ptr;
        m_ptr = nullptr;
    }
    
    // 禁用拷贝构造(避免所有权拷贝)
    SimpleSmartPtr(const SimpleSmartPtr&) = delete;
    // 禁用赋值运算符
    SimpleSmartPtr& operator=(const SimpleSmartPtr&) = delete;
    
    // 重载解引用运算符
    T& operator*() const {
        return *m_ptr;
    }
    
    // 重载箭头运算符
    T* operator->() const {
        return m_ptr;
    }
};

// 使用简易智能指针
void goodExample() {
    SimpleSmartPtr<int> p(new int(10)); // 接管内存
    *p = 20; // 正常使用
    // 函数结束,p析构 → 自动释放内存,无泄漏
}
        

二、C++11 核心智能指针详解

2.1 std::unique_ptr:独占式智能指针

std::unique_ptr 是 C++11 主推的独占所有权智能指针:

核心用法:


#include <memory> // 必须包含头文件
#include <iostream>

using namespace std;

int main() {
    // 1. 创建unique_ptr(推荐使用make_unique,C++14引入)
    unique_ptr<int> up1(new int(10)); 
    unique_ptr<int> up2 = make_unique<int>>(20); // 更安全,避免内存泄漏
    
    // 2. 访问数据
    cout << *up1 << endl; // 输出:10
    cout << *up2 << endl; // 输出:20
    
    // 3. 所有权转移(移动语义)
    unique_ptr<int> up3 = move(up1); // up1失去所有权,变为空
    if (up1 == nullptr) {
        cout << "up1已失去所有权" << endl;
    }
    cout << *up3 << endl; // 输出:10
    
    // 4. 释放所有权(手动)
    int* rawPtr = up3.release(); // up3释放所有权,返回裸指针
    delete rawPtr; // 需手动释放
    rawPtr = nullptr;
    
    // 5. 重置指针
    up2.reset(new int(30)); // 释放原有内存,指向新内存
    cout << *up2 << endl; // 输出:30
    
    up2.reset(); // 释放内存,up2变为空
    if (up2 == nullptr) {
        cout << "up2已为空" << endl;
    }
    
    return 0;
}
        

适用场景:

2.2 std::shared_ptr:共享式智能指针

std::shared_ptr共享所有权智能指针,核心特性:

核心用法:


#include <memory>
#include <iostream>

using namespace std;

int main() {
    // 1. 创建shared_ptr(推荐使用make_shared)
    shared_ptr<int> sp1(new int(10));
    shared_ptr<int> sp2 = make_shared<int>(20); // 更高效,一次分配内存
    
    // 2. 查看引用计数
    cout << "sp1引用计数:" << sp1.use_count() << endl; // 输出:1
    cout << "sp2引用计数:" << sp2.use_count() << endl; // 输出:1
    
    // 3. 拷贝:引用计数增加
    shared_ptr<int> sp3 = sp1;
    cout << "sp1引用计数:" << sp1.use_count() << endl; // 输出:2
    cout << "sp3引用计数:" << sp3.use_count() << endl; // 输出:2
    
    // 4. 修改数据(所有共享指针都可见)
    *sp3 = 100;
    cout << *sp1 << endl; // 输出:100
    
    // 5. 重置指针:引用计数减少
    sp1.reset();
    cout << "sp1引用计数:" << (sp1 ? sp1.use_count() : 0) << endl; // 输出:0
    cout << "sp3引用计数:" << sp3.use_count() << endl; // 输出:1
    
    // 6. 最后一个指针析构时,释放内存
    sp3.reset();
    cout << "sp3引用计数:" << (sp3 ? sp3.use_count() : 0) << endl; // 输出:0
    
    return 0;
}
        

引用计数原理:

shared_ptr 内部维护两个指针:

注意:避免用同一个裸指针创建多个 shared_ptr,会导致多次释放内存:
int* p = new int(10); shared_ptr sp1(p); shared_ptr sp2(p); → 双重释放!

2.3 std::weak_ptr:弱引用智能指针

std::weak_ptr 是为解决 shared_ptr 循环引用 问题而生的弱引用指针:

循环引用问题(无weak_ptr时):


// 循环引用导致内存泄漏
struct Node {
    shared_ptr<Node> next; // 共享指针指向另一个节点
    ~Node() { cout << "Node析构" << endl; }
};

void cycleRef() {
    shared_ptr<Node> n1 = make_shared<Node>();
    shared_ptr<Node> n2 = make_shared<Node>();
    
    n1->next = n2; // n2计数+1 → 2
    n2->next = n1; // n1计数+1 → 2
    
    // 函数结束,n1/n2析构 → 计数各减1 → 均为1
    // 计数不为0,内存未释放 → 泄漏
}
        

weak_ptr 解决循环引用:


#include <memory>
#include <iostream>

using namespace std;

// 修复循环引用:将其中一个指针改为weak_ptr
struct Node {
    weak_ptr<Node> next; // 弱引用,不增加计数
    ~Node() { cout << "Node析构" << endl; }
};

void fixCycleRef() {
    shared_ptr<Node> n1 = make_shared<Node>();
    shared_ptr<Node> n2 = make_shared<Node>();
    
    n1->next = n2; // weak_ptr,n2计数仍为1
    n2->next = n1; // weak_ptr,n1计数仍为1
    
    // 函数结束,n1/n2析构 → 计数减为0 → 内存释放,析构函数执行
}

// weak_ptr核心用法
void weakPtrUsage() {
    shared_ptr<int> sp = make_shared<int>(100);
    weak_ptr<int> wp = sp; // 弱引用,sp计数仍为1
    
    cout << "wp是否过期:" << boolalpha << wp.expired() << endl; // 输出:false
    
    // 转换为shared_ptr访问数据
    if (shared_ptr<int> sp2 = wp.lock()) {
        cout << *sp2 << endl; // 输出:100
        cout << "sp计数:" << sp.use_count() << endl; // 输出:2
    }
    
    // 释放sp,内存失效
    sp.reset();
    cout << "wp是否过期:" << boolalpha << wp.expired() << endl; // 输出:true
    
    // lock()返回空指针
    if (shared_ptr<int> sp2 = wp.lock()) {
        cout << *sp2 << endl;
    } else {
        cout << "内存已释放" << endl; // 输出:内存已释放
    }
}

int main() {
    fixCycleRef(); // 输出两次Node析构
    weakPtrUsage();
    return 0;
}
        

三、std::auto_ptr:被弃用的智能指针

3.1 std::auto_ptr 特性

std::auto_ptr 是 C++98 引入的第一个智能指针,设计目标是实现独占式内存管理,但存在严重设计缺陷,C++11 已弃用,C++17 正式移除。


// auto_ptr 基本用法(不推荐使用)
#include <memory>
#include <iostream>

using namespace std;

int main() {
    auto_ptr<int> ap(new int(10));
    cout << *ap << endl; // 输出:10
    
    // 拷贝赋值:所有权转移(隐藏的陷阱)
    auto_ptr<int> ap2 = ap;
    // ap已失去所有权,变为空
    if (ap.get() == nullptr) {
        cout << "ap已失去所有权" << endl;
    }
    cout << *ap2 << endl; // 输出:10
    
    return 0;
}
        

3.2 弃用原因详解

缺陷点 具体问题 危害
拷贝/赋值语义错误 拷贝/赋值时转移所有权,而非深拷贝,原指针变为空 原指针解引用会导致程序崩溃,逻辑难以追踪
不支持数组 析构时调用 delete 而非 delete[] 管理数组时导致内存泄漏/未定义行为
无法用于容器 容器拷贝/排序时会触发所有权转移,导致容器内指针失效 vector<auto_ptr<int>> 等用法会引发崩溃
无移动语义支持 C++11 引入移动语义后,auto_ptr 的拷贝语义与标准冲突 不符合现代C++设计理念,易与其他智能指针混淆
替代方案
- 独占式管理:使用 std::unique_ptr(禁用拷贝,显式移动);
- 共享式管理:使用 std::shared_ptr(引用计数,安全拷贝);
- 数组管理:使用 std::unique_ptr<T[]>(专门支持数组析构)。

四、智能指针的典型应用场景

五、使用注意事项

六、总结

智能指针是 C++ 内存管理的核心工具,掌握其用法和原理,能大幅提升代码的健壮性和可维护性,是现代 C++ 开发的必备技能。


返回顶部